import pygame
import random
import time
from pygame.locals import *
import weapons
import math
# uncomment and install psycho for better performances
#import psyco
#psyco.full()

#Constants

MAP_WIDTH=49
MAP_HEIGHT=37
TILE_WIDTH=16
TILE_HEIGHT=16
TIME_STEP=0.5
MAX_ASTAR=150
HIT_DURATION=0.2
NUM_ENEMIES=30


s=random.randint(0,1000)
#s=137
random.seed(s)
print "seed",s

class Digger:
	def __init__(self, m, x, y):
		self.LEFT_MAX=3
		self.RIGHT_MAX=3
		self.TOP_MAX=3
		self.BOTTOM_MAX=3
		self.CORRIDOR_LENGTH_MAX=15
		self.CORRIDOR_LENGTH_MIN=10
		self.terrain=m
		self.walls=[]
		self.corridor_ends=[(x,y)]
	
	def dig_room(self):
		if len(self.corridor_ends)==0:
			return False
		(x,y) = random.choice(self.corridor_ends)
		self.terrain[coord([x,y])]=0
		top=min(random.randint(1,self.TOP_MAX), y)
		bottom=min(random.randint(1,self.BOTTOM_MAX), MAP_HEIGHT-y-1)
		left=min(random.randint(1,self.LEFT_MAX), x)
		right=min(random.randint(1,self.RIGHT_MAX), MAP_WIDTH-x-1)
		for j in range(y-top, y+bottom):
			for i in range(x-left, x+right):
				self.terrain[coord([i,j])]=0
				
				
		for y in range(y-top, y+bottom):
			if x-left-1>=0:
				self.walls.append((x-left-1, y))
			if x+right+1<MAP_WIDTH:
				self.walls.append((x+right+1, y))
		for x in range(x-left, x+right):
			if y-top-1>=0:
				self.walls.append((x, y-top-1))
			if y+bottom+1<MAP_HEIGHT:
				self.walls.append((x, y+bottom+1))

	def dig_corridor(self):
		if len(self.walls)==0:
			return False
		
		(x,y)=random.choice(self.walls)
		if self.terrain[coord([x,y])]==0:
			while self.terrain[coord([x,y])]==0:
				self.walls.remove((x,y))
				if len(self.walls)==0:
					return False
				(x,y)=random.choice(self.walls)
				

		l=random.randint(self.CORRIDOR_LENGTH_MIN,self.CORRIDOR_LENGTH_MAX)
		dirs=[]
		if x>0 and self.terrain[coord([x-1,y])]!=0: dirs.append("l")
		if y>0 and self.terrain[coord([x,y-1])]!=0: dirs.append("u")
		if x<MAP_WIDTH-1 and self.terrain[coord([x+1,y])]!=0: dirs.append("r")
		if y<MAP_HEIGHT-1 and self.terrain[coord([x,y+1])]!=0: dirs.append("b")
		self.terrain[coord([x,y])]=0
		if len(dirs)==0:
			return False
		d=random.choice(dirs)
		for i in range(0,l):
			ax=x
			ay=y
			if d=="r": 
				x=x+1
				if x<0 or x>=MAP_WIDTH or y<0 or y>=MAP_HEIGHT:
					break				
				if y>0: self.walls.append((x,y-1))
				if y<MAP_HEIGHT-1: self.walls.append((x,y+1))
			elif d=="l": 
				x=x-1
				if x<0 or x>=MAP_WIDTH or y<0 or y>=MAP_HEIGHT:
					break				
				if y>0: self.walls.append((x,y-1))
				if y<MAP_HEIGHT-1: self.walls.append((x,y+1))

			elif d=="u": 
				y=y-1
				if x<0 or x>=MAP_WIDTH or y<0 or y>=MAP_HEIGHT:
					break				
				if x>0: self.walls.append((x-1,y))
				if x<MAP_WIDTH-1: self.walls.append((x+1,y))

			elif d=="d": 
				y=y+1
				if x<0 or x>=MAP_WIDTH or y<0 or y>=MAP_HEIGHT:
					break
				if x>0: self.walls.append((x-1,y))
				if x<MAP_WIDTH-1: self.walls.append((x+1,y))

			if x<0 or x>=MAP_WIDTH or y<0 or y>=MAP_HEIGHT:
				break
			self.terrain[coord([x,y])]=0
		self.corridor_ends.append((x,y))
			

	def dig(self):
		for k in range(0,10):
			self.dig_room()
			self.dig_corridor()
			self.dig_corridor()
			self.dig_corridor()
			self.dig_corridor()

class Game:
	def __init__(self):
		self.tileset=None
		self.tmap=None
		self.astar=None
		self.playerxy=[0,MAP_HEIGHT/2]
		self.playerhp=100
		self.playeraction=400
		self.enemies=None
		self.last_update=0
		self.last_astar_update=0
		self.occupied=None
		self.visible=None
		self.hits=[]
		self.finished=False
	
	def updateWalkable(self):
		self.walkable = [True]*len(self.tmap)
		for i in range(0,len(self.tmap)):
			if self.tmap[i]!=0:
				self.walkable[i]=False
		for e in self.enemies:
			self.walkable[coord(e.xy)]=False
		self.walkable[coord(game.playerxy)]=False

	def updateVisible(self):
		# 0 : unknown (default not visible)
		# 1 : visible
		# 2 : not visible
		#
		self.visible = [0]*len(self.tmap)
		self.visible[coord(game.playerxy)]=1
		x=game.playerxy[0]
		y=game.playerxy[1]

		
		#left
		if(x>0):
			for j in range(0,MAP_HEIGHT):
				if abs(j-y)>(x):
					d=float(x)/float(j-y)
					if j-y>0:
						ydest=j+1
						inc=1
					else:
						ydest=0
						inc=-1
					for i in range(y,ydest,inc):
						vx=min(max(int((y-i)*d+x),0),MAP_WIDTH-1)
						vy=i
						if game.tmap[coord([vx,vy])]!=1:
							game.visible[coord([vx,vy])]=1
						else:
							break
				else:
					d=float(j-y)/float(x)
					for i in range(x,-1,-1):
						vx=i
						vy=min(max(int((x-i)*d+y),0),MAP_HEIGHT-1)
						if game.tmap[coord([vx,vy])]!=1:
							game.visible[coord([vx,vy])]=1
						else:
							break

		#right
		if(x<MAP_WIDTH-1):
			for j in range(0,MAP_HEIGHT):
				
				if abs(j-y)>(MAP_WIDTH-1-x):
					d=float(MAP_WIDTH-1-x)/float(j-y)
					if j-y>0:
						ydest=j+1
						inc=1
					else:
						ydest=0
						inc=-1
					for i in range(y,ydest,inc):
						vx=min(max(int((i-y)*d+x),0),MAP_WIDTH-1)
						vy=i
						if game.tmap[coord([vx,vy])]!=1:
							game.visible[coord([vx,vy])]=1
						else:
							break
				else:
					d=float(j-y)/float(MAP_WIDTH-1-x)
					for i in range(x,MAP_WIDTH,1):
						vx=i
						vy=min(max(int((i-x)*d+y),0),MAP_HEIGHT-1)
						if game.tmap[coord([vx,vy])]!=1:
							game.visible[coord([vx,vy])]=1
						else:
							break

		#up
		if(y>0):
			for j in range(0,MAP_WIDTH):
				if abs(j-x)>(y):
					d=float(y)/float(j-x)
					if j-x>0:
						xdest=j+1
						inc=1
					else:
						xdest=0
						inc=-1
					for i in range(x,xdest,inc):
						vy=min(max(int((x-i)*d+y),0),MAP_HEIGHT-1)
						vx=i
						if game.tmap[coord([vx,vy])]!=1:
							game.visible[coord([vx,vy])]=1
						else:
							break
				else:
					d=float(j-x)/float(y)
					for i in range(y,-1,-1):
						vy=i
						vx=min(max(int((y-i)*d+x),0),MAP_WIDTH-1)
						if game.tmap[coord([vx,vy])]!=1:
							game.visible[coord([vx,vy])]=1
						else:
							break

		#down
		if(y<MAP_HEIGHT-1):
			for j in range(0,MAP_WIDTH):
				if abs(j-x)>(MAP_HEIGHT-1-y):
					d=float(MAP_HEIGHT-1-y)/float(j-x)
					if j-x>0:
						xdest=j+1
						inc=1
					else:
						xdest=0
						inc=-1
					for i in range(x,xdest,inc):
						vy=min(max(int((i-x)*d+y),0),MAP_HEIGHT-1)
						vx=i
						if game.tmap[coord([vx,vy])]!=1:
							game.visible[coord([vx,vy])]=1
						else:
							break
				else:

					d=float(j-x)/float(MAP_HEIGHT-1-y)
					for i in range(y,MAP_HEIGHT,1):
						vy=i
						vx=min(max(int((i-y)*d+x),0),MAP_WIDTH-1)
						if game.tmap[coord([vx,vy])]!=1:
							game.visible[coord([vx,vy])]=1
						else:
							break
		
	def playerHit(self,dmg):
		game.hits.append([time.time() ,game.playerxy,1])
		game.playerhp=game.playerhp-dmg
		if game.playerhp<=0:
			game.finished=True
		return


class Enemy:
	def __init__(self):
		self.species=1				# 1 is for rats
		self.xy=[0,0]
		self.last_update=time.time()+random.randint(0,1000)/1000.0
	
	def decideDirection(self, game):
		bestcost=game.astar[coord(self.xy)]+1		# We slightly prefer movement to immobility
		dirs=[[0,0]]
		# With astar
		if(self.xy[0]-1>=0) and game.walkable[coord(self.xy)-1]:
			if game.astar[coord(self.xy)-1]<bestcost:
				dirs=[[-1,0]]
				bestcost=game.astar[coord(self.xy)-1]
			else:
				if game.astar[coord(self.xy)-1]==bestcost:
					dirs.append([-1,0])
			
		if(self.xy[0]+1<MAP_WIDTH) and game.walkable[coord(self.xy)+1]:
			if game.astar[coord(self.xy)+1]<bestcost:
				dirs=[[1,0]]
				bestcost=game.astar[coord(self.xy)+1]
			else:
				if game.astar[coord(self.xy)+1]==bestcost:
				 	dirs.append([1,0])
			
		if(self.xy[1]-1>=0) and game.walkable[coord(self.xy)-MAP_WIDTH]:
			if game.astar[coord(self.xy)-MAP_WIDTH]<bestcost:
				dirs=[[0,-1]]
				bestcost=game.astar[coord(self.xy)-MAP_WIDTH]
			else:
				if game.astar[coord(self.xy)-MAP_WIDTH]==bestcost:
					dirs.append([0,-1])
			
		if(self.xy[1]+1<MAP_HEIGHT) and game.walkable[coord(self.xy)+MAP_WIDTH]:
			if game.astar[coord(self.xy)+MAP_WIDTH]<bestcost:
				dirs=[[0,+1]]
				bestcost=game.astar[coord(self.xy)+MAP_WIDTH]
			else:
				if game.astar[coord(self.xy)+MAP_WIDTH]==bestcost:
					dirs.append([0,1])
		
		direction = random.choice(dirs)
				
		if bestcost>9000:
			dirs=[[0,0]]
			if self.xy[0]<game.playerxy[0] and game.walkable[coord([self.xy[0]+1,self.xy[1]])]:
				dirs.append([1,0])
			if self.xy[0]>game.playerxy[0] and game.walkable[coord([self.xy[0]-1,self.xy[1]])]:
				dirs.append([-1,0])
			if self.xy[1]<game.playerxy[1] and game.walkable[coord([self.xy[0],self.xy[1]+1])]:
				dirs.append([0,1])
			if self.xy[1]>game.playerxy[1] and game.walkable[coord([self.xy[0],self.xy[1]-1])]:
				dirs.append([0,-1])
			if len(dirs)>0:
				direction=random.choice(dirs)
		return direction
			
	def update(self, game):
		self.last_update=time.time()
		if (abs(self.xy[0]-game.playerxy[0])+abs(self.xy[1]-game.playerxy[1]))==1:
			game.playerHit(5)

		else:
			d=self.decideDirection(game)
			newpos=[self.xy[0]+d[0], self.xy[1]+d[1]]
			if game.walkable[coord(newpos)]:
				self.xy=newpos
				game.walkable[coord(newpos)]=False

def coord(c):
	return c[0]+c[1]*MAP_WIDTH



### Init

game=Game()

# Map 

#terrain_map=[1]*(MAP_WIDTH*MAP_HEIGHT)

d=Digger([1]*(MAP_WIDTH*MAP_HEIGHT), game.playerxy[0], game.playerxy[1])
d.dig()
terrain_map=d.terrain

"""
p=game.playerxy
terrain_map[coord(p)]=0
for k in range(0,155):
	if random.choice([1,2])==1:
		top=random.randint(-5,0)
		bottom=random.randint(0,5)
		left=random.randint(-5,0)
		right=random.randint(0,5)
		if left+p[0]<0:
			left=-p[0]
		if right+p[0]>=MAP_WIDTH:
			right=MAP_WIDTH-p[0]-1
		if top+p[1]<0:
			top=-p[1]
		if bottom+p[1]>=MAP_HEIGHT:
			bottom=MAP_HEIGHT-p[1]-1
		for x in range(left+p[0],right+p[0]):
			for y in range(top+p[1],bottom+p[1]):
				terrain_map[coord([x,y])]=0
		p=[random.randint(left+p[0],right/2+p[0]),random.randint(top+p[1],bottom/2+p[1])]
		terrain_map[coord(p)]=0
	else:
		d=random.choice([0,1,2,3])
		for i in range(0,random.randint(5,15)):
			if d==3 and p[1]>0:
				p[1]=p[1]-1
				terrain_map[coord(p)]=0
			if d==2 and p[0]>0:
				p[0]=p[0]-1
				terrain_map[coord(p)]=0
			if d==1 and p[1]<MAP_HEIGHT-1:
				p[1]=p[1]+1
				terrain_map[coord(p)]=0
			if d==0 and p[0]<MAP_WIDTH-1:
				p[0]=p[0]+1
				terrain_map[coord(p)]=0
"""

# Enemies
enemies=[]
for i in range(0,NUM_ENEMIES):
	enemy=Enemy()
	enemy.xy[0]=random.randint(0,MAP_WIDTH-1)
	enemy.xy[1]=random.randint(0,MAP_HEIGHT-1)
	while terrain_map[coord(enemy.xy)]!=0:
		enemy.xy[0]=random.randint(0,MAP_WIDTH-1)
		enemy.xy[1]=random.randint(0,MAP_HEIGHT-1)
	enemies.append(enemy)


# pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption('Dwarf Warrior')

# tileset
tileset=[]
tileset.append(pygame.image.load("img/empty.png"))
tileset.append(pygame.image.load("img/wall.png"))

#sprites
sprites=[]
sprites.append(pygame.image.load("img/dwarfwarrior.png"))
sprites.append(pygame.image.load("img/enemy1.png"))
sprites.append(pygame.image.load("img/strike.png"))
sprites.append(pygame.image.load("img/strike_red.png"))

# Game structure
game.tileset=tileset
game.tmap=terrain_map
game.sprites=sprites
game.enemies=enemies
game.visible=[0]*len(game.tmap)
game.updateWalkable()
game.updateVisible()


def astar(game):
	pool=[]

	# Every square is really far from the player
	game.astar=[10000]*len(game.tmap)
	
	# The square under the player has the zero distance
	game.astar[coord(game.playerxy)]=0
	
	# Then we populate the pool with the 4 squares neighbooring the player
	if game.playerxy[0]-1>=0:
		if game.tmap[game.playerxy[0]-1 + game.playerxy[1]*MAP_WIDTH]==0:
			pool.append([game.playerxy[0]-1,game.playerxy[1]])
	
	if game.playerxy[0]+1<MAP_WIDTH:
		if game.tmap[game.playerxy[0]+1 + game.playerxy[1]*MAP_WIDTH]==0:
			pool.append([game.playerxy[0]+1,game.playerxy[1]])
	
	if game.playerxy[1]-1>=0:
		if game.tmap[game.playerxy[0] + (game.playerxy[1]-1)*MAP_WIDTH]==0:
			pool.append([game.playerxy[0],game.playerxy[1]-1])
	
	if game.playerxy[1]+1<MAP_HEIGHT:
		if game.tmap[game.playerxy[0] + (game.playerxy[1]+1)*MAP_WIDTH]==0:
			pool.append([game.playerxy[0],game.playerxy[1]+1])
	
	
	# Now make the pool grow using the same logic
	count=0
	while len(pool)>0:
		count=count+1
		p=pool.pop(0)
		possiblevalues=[10000]
		if p[0]-1 >= 0:
			if game.tmap[p[0]-1 + p[1]*MAP_WIDTH]==0:
				if game.astar[p[0]-1 + p[1]*MAP_WIDTH]==10000:
					pool.append([p[0]-1,p[1]])
					game.astar[p[0]-1 + p[1]*MAP_WIDTH]=10001
				else:
					possiblevalues.append(game.astar[p[0]-1 + p[1]*MAP_WIDTH])
		
		if p[0]+1 < MAP_WIDTH:
			if game.tmap[p[0]+1 + p[1]*MAP_WIDTH]==0:
				if game.astar[p[0]+1 + p[1]*MAP_WIDTH]==10000:
					pool.append([p[0]+1,p[1]])
					game.astar[p[0]+1 + p[1]*MAP_WIDTH]=10001
				else:
					possiblevalues.append(game.astar[p[0]+1 + p[1]*MAP_WIDTH])
		
		if p[1]-1 >= 0:
			if game.tmap[p[0] + (p[1]-1)*MAP_WIDTH]==0:
				if game.astar[p[0] + (p[1]-1)*MAP_WIDTH]==10000:
					pool.append([p[0],p[1]-1])
					game.astar[p[0] + (p[1]-1)*MAP_WIDTH]==10001
				else:
					possiblevalues.append(game.astar[p[0] + (p[1]-1)*MAP_WIDTH])
		
		if p[1]+1 < MAP_HEIGHT:
			if game.tmap[p[0] + (p[1]+1)*MAP_WIDTH]==0:
				if game.astar[p[0] + (p[1]+1)*MAP_WIDTH]==10000:
					pool.append([p[0],p[1]+1])
					game.astar[p[0] + (p[1]+1)*MAP_WIDTH]==10001
				else:
					possiblevalues.append(game.astar[p[0] + (p[1]+1)*MAP_WIDTH])
		
		game.astar[coord(p)]=min(possiblevalues)+1
		if game.astar[coord(p)]>MAX_ASTAR:
			return



def update(game):
	current = time.time()
	for s in game.hits:
		if current-s[0] > HIT_DURATION:
			game.hits.remove(s)
		
	
	if current - game.last_astar_update > TIME_STEP:
		astar(game)
		game.last_astar_update=current
		#if game.walkable[coord([10,10])]:
		#	e=Enemy()
		#	e.xy=[10,10]
		#	game.enemies.append(e)
	for e in game.enemies:
		if current-e.last_update > TIME_STEP:
			e.update(game)
	game.updateWalkable()
	game.playeraction += (current-game.last_update)*100
	if game.playeraction>400:
		game.playeraction=400 
	game.last_update=current
		
		
		

def draw(screen, game):
	screen.fill([0,0,0])
	for y in range(0, MAP_HEIGHT):
		for x in range(0, MAP_WIDTH):
			
			if game.visible[coord([x,y])]==1:
				screen.fill((32,32,32), pygame.Rect(x*TILE_WIDTH, y*TILE_HEIGHT,16,16))
			if game.tmap[x+y*MAP_WIDTH]!=0:
				screen.blit(game.tileset[game.tmap[x+y*MAP_WIDTH]],(x*TILE_WIDTH, y*TILE_HEIGHT))
			#if game.astar[x+y*MAP_WIDTH]>0 and game.astar[x+y*MAP_WIDTH]<64:
			#	screen.fill((256-game.astar[x+y*MAP_WIDTH]*4,32,32), pygame.Rect(x*TILE_WIDTH, y*TILE_HEIGHT,16,16))
			
	screen.blit(game.sprites[0], (game.playerxy[0]*TILE_WIDTH, game.playerxy[1]*TILE_HEIGHT))
	for e in game.enemies:
		if game.visible[coord(e.xy)]==1:
			screen.blit(game.sprites[e.species], (e.xy[0]*TILE_WIDTH, e.xy[1]*TILE_HEIGHT))
	for s in game.hits:
		screen.blit(game.sprites[2+s[2]], (s[1][0]*TILE_WIDTH, s[1][1]*TILE_HEIGHT))
	screen.fill([0,200,0], Rect(788,20,10,game.playerhp))
	screen.fill([0,0,200], Rect(788,200,10,game.playeraction/4))
	for i in range(1,4):
		screen.fill([0,0,0], Rect(788,200+i*25,10,1))

def move(game, (dx,dy)):
	newcoords=[game.playerxy[0]+dx, game.playerxy[1]+dy]
	if newcoords[0]<0 or newcoords[0]>=MAP_WIDTH or newcoords[1]<0 or newcoords[1]>=MAP_HEIGHT:
		return
	if game.walkable[newcoords[0]+newcoords[1]*MAP_WIDTH]:
		game.playerxy=newcoords
		game.walkable[newcoords[0]+newcoords[1]*MAP_WIDTH]=False
		game.updateVisible()

def hit(game, (dx,dy), attack):
	if game.playeraction>=100:
		for c in weapons.sword[attack]:
			rx=dx*c[0]+dy*c[1]
			ry=dy*c[0]+dx*c[1]
			if game.playerxy[0]+rx>=0 and game.playerxy[0]+rx<MAP_WIDTH and game.playerxy[1]+ry>=0 and game.playerxy[1]+ry<MAP_HEIGHT:
				hitsat=(game.playerxy[0]+rx, game.playerxy[1]+ry)
				game.hits.append([time.time() ,hitsat,0])
				if(not game.walkable[coord(hitsat)]) and game.tmap[coord(hitsat)]==0:
					for e in game.enemies:
						if e.xy[0]==hitsat[0] and e.xy[1]==hitsat[1]:
							game.enemies.remove(e)
		game.playeraction-=100
	return

### Mainloop

game.finished=False

while not game.finished:
	
	update(game)
	draw(screen, game)
	pygame.display.flip()
	
	for event in pygame.event.get():
		if event.type == QUIT:
			game.finished=True
		elif event.type == KEYDOWN:
			if event.key == K_ESCAPE:
				game.finished=True
			if(event.mod & 0x01)==1:	
				if event.key == K_UP:
					hit(game, (0,-1),0)
				if event.key == K_DOWN:
					hit(game,(0,1),0)
				if event.key == K_LEFT:
					hit(game,(-1,0),0)
				if event.key == K_RIGHT:
					hit(game,(1,0),0)
			elif(event.mod & 0x64)!=0:
				if event.key == K_UP:
					hit(game, (0,-1),1)
				if event.key == K_DOWN:
					hit(game,(0,1),1)
				if event.key == K_LEFT:
					hit(game,(-1,0),1)
				if event.key == K_RIGHT:
					hit(game,(1,0),1)
			else:
				if event.key == K_UP:
					move(game, (0,-1))
				if event.key == K_DOWN:
					move(game,(0,1))
				if event.key == K_LEFT:
					move(game,(-1,0))
				if event.key == K_RIGHT:
					move(game,(1,0))

print "You killed", NUM_ENEMIES-len(game.enemies), "enemies.", len(game.enemies), "were still alive." 














